﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ClearUnit.Exceptions;
using System.Linq.Expressions;
using System.Diagnostics;
using Common.Net.DesignByContract;
using Common.Net.DataStructures;
using Common.Net.Utilities;

namespace ClearUnit
{
    public static class Assert
    {
        #region Formatting
        private static string FormatExpression(Expression<Func<bool>> e)
        {
            return ExpressionFormatter.Format(e);
        }
        #endregion

        public static void IsTrue(bool condition)
        {
            Assert.IsTrue(condition, "True expected");
        }

        public static void IsFalse(bool condition)
        {
            Assert.IsFalse(condition, "False expected");
        }

        public static void IsTrue(bool condition, string format, params object[] args)
        {
            Assert.BoolsAreEqual(true, condition, format, args);
        }

        public static void IsFalse(bool condition, string format, params object[] args)
        {
            Assert.BoolsAreEqual(false, condition, format, args);
        }

        public static void IsTrue(Expression<Func<bool>> condition)
        {
            Assert.IsTrue(condition.Compile()(), "{0} must be true", FormatExpression(condition));
        }

        public static void IsFalse(Expression<Func<bool>> condition)
        {
            Assert.IsFalse(condition.Compile()(), "{0} must be false", FormatExpression(condition));
        }

        public static void AreEqual<T>(T expected, T actual)
        {
            Assert.AreEqual(expected, actual, "");
        }

        public static void AreNotEqual<T>(T expected, T actual)
        {
            Assert.AreNotEqual(expected, actual, "");
        }

        public static void AreNotEqual<T>(T expected, T actual, string format, params object[] args)
        {
            if (object.Equals(expected, actual))
            {
                string message = format.Format(args);
                throw AssertionExceptionFactory.Assertion("Value '{0}' was not expected. {1}", actual, message);
            }
        }

        public static void AreEqual<T>(T expected, T actual, string format, params object[] args)
        {
            if (!object.Equals(expected, actual))
                throw AssertionExceptionFactory.OtherValueExpected(expected, actual, format, args);
        }

        static void BoolsAreEqual(bool expected, bool actual, string format, params object[] args)
        {
            if (expected != actual)
                throw AssertionExceptionFactory.OtherBoolValueExpected(format, args);
        }

        public static void IsInstanceOfType(Type expected, object value)
        {
            RequireArg.NotNull(() => expected);
            RequireArg.NotNull(() => value);

            Type actual = value.GetType();

            if (!expected.IsAssignableFrom(actual))
                throw AssertionExceptionFactory.OtherTypeExpected(expected, actual);
        }

        public static T IsInstanceOfType<T>(object value)
        {
            IsInstanceOfType(typeof(T), value);

            return (T)value;
        }

        #region Exceptions

        class VoidValue
        {
            public static readonly VoidValue Instance = new VoidValue();
            VoidValue() { }
            public override string ToString() { return "void"; }
        }

        public static void DoesNotThrow(Action act)
        {
            try
            {
                act();
            }
            catch (Exception ex)
            {
                throw new AssertionException("Exception was thrown", ex);
            }
        }

        static Func<object> GetFunc(Action act)
        {
            return () =>
            {
                act();
                return VoidValue.Instance;
            };
        }

        public static Exception Throws(Action act)
        {
            return Throws(GetFunc(act));
        }

        public static TEx Throws<TEx>(Action act)
            where TEx : Exception
        {
            return Throws<TEx>(GetFunc(act));
        }

        public static Exception Throws(Func<object> act)
        {
            return Throws<Exception>(act, true);
        }

        public static TEx Throws<TEx>(Func<object> act)
            where TEx : Exception
        {
            return Throws<TEx>(act, false);
        }

        public static TEx Throws<TEx>(Func<object> act, bool allowInherited)
            where TEx : Exception
        {
            var callResult = FunctionCallResult.Get(act);

            if (callResult.DidNotThrow)
                throw AssertionExceptionFactory.AnyExceptionExpected<TEx>(callResult.ReturnValue);

            bool correctException =
                (allowInherited && callResult.ThrownException is TEx) ||
                (!allowInherited && (callResult.ThrownException.GetType() == typeof(TEx)));

            if (!correctException)
                throw AssertionExceptionFactory.OtherExceptionExpected<TEx>(callResult.ThrownException);

            return (TEx)callResult.ThrownException;
        }

        #endregion

        #region Collections
        public static void SetsAreEqual<T>(IEnumerable<T> actual, params T[] expected)
        {
            SetsAreEqual(actual, expected.AsEnumerable());
        }

        public static void SetsAreEqual<T>(IEnumerable<T> actual, IEnumerable<T> expected)
        {
            Assert.IsTrue(EnumerableHelper.AreSequencesEqual(actual, expected).AreEqual, "Sets must be equal");
        }
        #endregion
    }
}
